package db

import (
	"errors"
	"runtime/debug"
	"sort"
	"strings"

	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/orm"
)

func listModel(context def.Context, app string, tableName string, fields []string, keyword string, ids []string, filter map[string][]string, like []string, page int, size int, all bool, order string, direction string) (*web.PageResult, error) {
	var list []map[string]interface{}
	var err error
	var table *Table
	var schema *orm.Schema
	var query orm.ModelQuery
	var count int64
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return nil, SystemException(err)
	}
	schema = table.Schema()
	query = table.Query()
	primarys := schema.GetPrimary()
	f, _ := schema.GetSqlColumn(primarys[0])
	if ids != nil && len(ids) > 0 {
		if primarys == nil || len(primarys) == 0 {
			return nil, SystemExceptionDetail("主键不存在", err)
		}
		query.Where("in", f, ids)
	}
	if fields != nil && len(fields) > 0 {
		for _, field := range fields {
			query.Field(field)
		}
		for _, field := range primarys {
			f, _ := schema.GetSqlColumn(field)
			query.Field(f)
		}
	}
	if keyword != "" {
		keywordLike := "%" + keyword + "%"
		searchFields := schema.TagWithFields("db", "search")
		primarys := schema.GetPrimary()
		values := make([]interface{}, len(primarys)+len(searchFields))
		sqlArray := make([]string, len(primarys)+len(searchFields))

		for _, field := range primarys {
			f, _ := schema.GetSqlColumn(field)
			sqlArray = append(sqlArray, "`"+f+"` = ?")
			values = append(values, keyword)
		}
		for _, field := range searchFields {
			f, _ := schema.GetSqlColumn(field)
			sqlArray = append(sqlArray, "`"+f+"` like ?")
			values = append(values, keywordLike)
		}
		query.WhereSql(strings.Join(sqlArray, "or"), values...)
	}
	if filter != nil {
		for key, value := range filter {
			index := sort.SearchStrings(like, key)
			if index >= 0 && index < len(like) {
				query.Where("like", key, strings.Join(value, ""))
			} else {
				query.Where("in", key, value)
			}
		}
	}
	if order != "" {
		if direction == orm.DESC {
			query.Desc(order)
		} else if direction == orm.ASC {
			query.Asc(order)
		}
	} else {
		if table.Sort != "" {
			query.Asc(table.Sort)
		}
	}
	if all || size <= 0 {
		list, err = query.SelectInterface()
		if err != nil {
			return nil, SystemException(err)
		}
		count = int64(len(list))
	} else {
		count, list, err = query.SelectAndCount(page, size)
		if err != nil {
			return nil, SystemException(err)
		}
	}
	if table.Format != nil {
		for _, item := range list {
			table.Format(item)
		}
	}
	if table.Link != "" {
		// 查找关联
		linkTable, err := GetLink(app)
		if err != nil {
			return nil, SystemException(err)
		}
		ids := make([]string, len(list))
		for index, item := range list {
			ids[index] = utils.ToString(item[f])
		}
		var links []Link
		if _, err := linkTable.Query().Where("=", "source_table", tableName).Where("in", "source_id", ids).Find(&links); err != nil {
			return nil, SystemException(err)
		}
		linkMap := make(map[string]*Link, len(links))
		for index := range links {
			link := &links[index]
			linkMap[link.SourceId] = link
		}
		//fmt.Println(linkMap)
		for _, item := range list {
			link := linkMap[utils.ToString(item[f])]
			//fmt.Println(utils.ToString(item[f]), link)
			if link == nil {
				continue
			}
			item[table.Link] = LinkInfo{
				Table: link.TargetTable,
				Id:    link.TargetId,
				Desc:  link.Desc,
			}
		}
	}
	if table.Linked != "" {
		// 查找被关联
		linkTable, err := GetLink(app)
		if err != nil {
			return nil, SystemException(err)
		}
		ids := make([]string, len(list))
		for index, item := range list {
			ids[index] = utils.ToString(item[f])
		}
		var links []Link
		if _, err := linkTable.Query().Where("=", "target_table", tableName).Where("in", "target_id", ids).Find(&links); err != nil {
			return nil, SystemException(err)
		}
		linkMap := make(map[string]*Link, len(links))
		for index := range links {
			link := &links[index]
			linkMap[link.TargetId] = link
		}
		for _, item := range list {
			link := linkMap[utils.ToString(item[f])]
			if link == nil {
				continue
			}
			item[table.Linked] = LinkInfo{
				Table: link.TargetTable,
				Id:    link.TargetId,
				Desc:  link.Desc,
			}
		}
	}
	return web.ResponsePage(list, page, int(count)), nil
}

func sortModel(context def.Context, app string, tableName string, id string, sorted string) error {
	var err error
	var table *Table
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return SystemException(err)
	}
	if table.Sort == "" {
		return SystemExceptionDetail("无排序字段", err)
	}
	info, err := getModel(context, app, tableName, id)
	if err != nil {
		return SystemException(err)
	}
	target, err := getModel(context, app, tableName, sorted)
	if err != nil {
		return SystemException(err)
	}
	t, err := table.Begin()
	if err != nil {
		return SystemException(err)
	}
	if _, err = table.Session(t).Where(">", table.Sort, info[table.Sort]).Update(map[string]interface{}{
		table.Sort: &orm.UpdateOperate{Operate: "-", Value: 1},
	}); err != nil {
		goto Rollback
	}
	if _, err = table.Session(t).Where(">=", table.Sort, target[table.Sort]).Update(map[string]interface{}{
		table.Sort: &orm.UpdateOperate{Operate: "+", Value: 1},
	}); err != nil {
		goto Rollback
	}
	if _, err = table.PK(id).Session(t).Update(map[string]interface{}{
		table.Sort: target[table.Sort],
	}); err != nil {
		goto Rollback
	}
	if err := t.Commit(); err != nil {
		goto Rollback
	}
	return nil
Rollback:
	_ = t.Rollback()
	return SystemExceptionDetail("排序错误", err)
}

func linkModel(context def.Context, app string, tableName string, id string, targetTableName string, targetId string, desc string) error {
	linkModel, err := GetLink(app)
	if err != nil {
		return SystemException(err)
	}
	t, err := linkModel.Begin()
	if err != nil {
		return SystemException(err)
	}

	if _, err := linkModel.Session(t).Where("=", "source_table", tableName).Where("=", "source_id", id).Delete(); err != nil {
		goto Rollback
	}
	if targetTableName != "" && targetId != "" {
		if _, err := linkModel.Session(t).Where("=", "target_table", targetTableName).Where("=", "target_id", targetId).Delete(); err != nil {
			goto Rollback
		}
		if _, err := linkModel.Session(t).Insert(map[string]interface{}{
			"source_table": tableName,
			"source_id":    id,
			"target_table": targetTableName,
			"target_id":    targetId,
			"desc":         desc,
		}); err != nil {
			goto Rollback
		}
	}
	if err := t.Commit(); err != nil {
		goto Rollback
	}
	return nil
Rollback:
	_ = t.Rollback()
	return SystemExceptionDetail("关联错误", err)
}

func addModel(context def.Context, app string, tableName string, data map[string]interface{}) (map[string]interface{}, error) {
	var err error
	var table *Table
	var schema *orm.Schema
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return nil, SystemException(err)
	}
	schema = table.Schema()
	primarys := schema.GetPrimary()
	if primarys == nil || len(primarys) == 0 {
		return nil, SystemExceptionDetail("主键不存在", err)
	}

	if table.CanAdd != nil {
		if err := table.CanAdd(data); err != nil {
			return nil, ValidException(err)
		}
	}
	if _, ok := table.Model.Schema().GetSqlColumn("app"); !ok {
		delete(data, "app")
	}

	f, _ := schema.GetSqlColumn(primarys[0])
	var id string
	t, err := table.Begin()
	if err != nil {
		return nil, SystemException(err)
	}
	if _, err = table.Query().Session(t).Insert(data); err != nil {
		goto Rollback
	}
	id = utils.ToString(data[f])
	if table.Sort != "" {
		if _, err = table.PK(id).Session(t).Update(map[string]interface{}{
			table.Sort: id,
		}); err != nil {
			goto Rollback
		}
	}
	if err := t.Commit(); err != nil {
		goto Rollback
	}
	return getModel(context, app, tableName, id)
Rollback:
	_ = t.Rollback()
	debug.PrintStack()
	return nil, SystemExceptionDetail("添加失败", err)
}

func getModel(context def.Context, app string, tableName string, id string) (map[string]interface{}, error) {
	var entity map[string]interface{}
	var err error
	var table *Table
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return nil, SystemException(err)
	}
	if entity, err = table.PK(id).OneInterface(); err != nil {
		return nil, EmptyExceptionDetail("id不存在", err)
	}
	if table.Format != nil {
		table.Format(entity)
	}
	if table.Link != "" {
		linkModel, err := GetLink(app)
		if err != nil {
			return nil, SystemException(err)
		}
		var link Link
		if f, err := linkModel.Query().Where("=", "source_table", tableName).Where("=", "source_id", id).One(&link); err != nil {
			return nil, SystemException(err)
		} else if f {
			entity[table.Link] = &LinkInfo{
				Table: link.TargetTable,
				Id:    link.TargetId,
				Desc:  link.Desc,
			}
		}
	}
	if table.Linked != "" {
		linkModel, err := GetLink(app)
		if err != nil {
			return nil, SystemException(err)
		}
		var link Link
		if f, err := linkModel.Query().Where("=", "target_table", tableName).Where("=", "target_id", id).One(&link); err != nil {
			return nil, SystemException(err)
		} else if f {
			entity[table.Linked] = &LinkInfo{
				Table: link.SourceTable,
				Id:    link.SourceId,
				Desc:  link.Desc,
			}
		}
	}
	return entity, nil
}

func editModel(context def.Context, app string, tableName string, id string, data map[string]interface{}) error {
	var err error
	var ok int64
	var table *Table
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return SystemException(err)
	}
	if _, ok := table.Model.Schema().GetSqlColumn("app"); !ok {
		delete(data, "app")
	}
	if table.CanEdit != nil {
		if err := table.CanEdit(id, data); err != nil {
			return ValidException(err)
		}
	}
	if table.Sort != "" {
		delete(data, table.Sort)
	}
	if ok, _ = table.PK(id).Count(); ok == 0 {
		if _, err = table.Query().Insert(data); err != nil {
			return SystemExceptionDetail("添加失败", err)
		}
	} else {
		if _, err = table.PK(id).Update(data); err != nil {
			return SystemExceptionDetail("更新失败", err)
		}
	}
	return nil
}

func deleteModel(context def.Context, app string, tableName string, id string) error {
	var err error
	var ok int64
	var table *Table
	table, err = FindTableFrom(tableName, app)
	if err != nil {
		return SystemException(err)
	}
	if ok, err = table.PK(id).Count(); ok == 0 {
		return EmptyExceptionDetail("id不存在", err)
	}

	if table.CanDelete != nil {
		if err := table.CanDelete(id); err != nil {
			return ValidException(err)
		}
	}
	var sortId int
	t, err := table.Begin()
	if err != nil {
		return SystemException(err)
	}
	if table.Sort != "" {
		info, err := getModel(context, app, tableName, id)
		if err != nil {
			return SystemException(err)
		}
		sortId = utils.ToInt(info[table.Sort])
	}

	if _, err = table.PK(id).Session(t).Delete(); err != nil {
		goto Rollback
	}
	if table.Sort != "" {
		if _, err = table.Query().Where(">", table.Sort, sortId).Update(map[string]interface{}{
			table.Sort: &orm.UpdateOperate{Operate: "-", Value: 1},
		}); err != nil {
			goto Rollback
		}
	}
	if table.Link != "" {
		linkTable, err := GetLink(app)
		if err != nil {
			return SystemException(err)
		}
		if _, err := linkTable.Session(t).Where("=", "source_table", tableName).Where("=", "source_id", id).Delete(); err != nil {
			goto Rollback
		}
	}
	if table.Linked != "" {
		linkTable, err := GetLink(app)
		if err != nil {
			return SystemException(err)
		}
		if _, err := linkTable.Session(t).Where("=", "target_table", tableName).Where("=", "target_id", id).Delete(); err != nil {
			goto Rollback
		}
	}
	if err := t.Commit(); err != nil {
		goto Rollback
	}
	return nil
Rollback:
	_ = t.Rollback()
	return SystemExceptionDetail("删除失败", err)
}

func TableRouter(builder composer.Builder, r *SwaggerRouter) error {
	return TableRouterWith(builder, r, tmpApp)
}

func TableRouterWith(builder composer.Builder, r *SwaggerRouter, app string) error {
	// 执行前已经完成所有表的注入
	// 根据表的特殊情况，注入外部controller
	tableMap, ok := appMap[app]
	if !ok {
		builder.Errorf("[ DB ] App: %s is not exist, no bind CURD", app)
		return errors.New("Table router need ")
	}
	for tableName, table := range tableMap {
		shortName := ShortTableName(tableName, app)
		if table.List != nil && table.List.Controller != nil {
			info := Swagger{
				Controller: table.List.Controller,
				Params:     table.List.Params,
				Response:   PageResponse(nil),
			}
			r.Get("/"+tableName+"/list", info)
			if shortName != tableName {
				r.Get("/"+shortName+"/{id}", info)
			}
		}
		if table.Add != nil && table.Add.Controller != nil {
			info := Swagger{
				Controller: table.Add.Controller,
				Params:     table.Add.Params,
				Response:   ApiResponse,
			}
			r.Post("/"+tableName+"/", info)
			if shortName != tableName {
				r.Post("/"+shortName+"/{id}", info)
			}
		}
		if table.Get != nil && table.Get.Controller != nil {
			info := Swagger{
				Controller: table.Get.Controller,
				Params:     table.Get.Params,
				Response:   ApiResponse,
			}
			r.Post("/"+tableName+"/{id}", info)
			if shortName != tableName {
				r.Post("/"+shortName+"/{id}", info)
			}
		}
		if table.Edit != nil && table.Edit.Controller != nil {
			info := Swagger{
				Controller: table.Edit.Controller,
				Params:     table.Edit.Params,
				Response:   ApiResponse,
			}
			r.Put("/"+tableName+"/{id}", info)
			if shortName != tableName {
				r.Put("/"+shortName+"/{id}", info)
			}
		}
		if table.Delete != nil && table.Delete.Controller != nil {
			info := Swagger{
				Controller: table.Delete.Controller,
				Params:     table.Delete.Params,
				Response:   ApiResponse,
			}
			r.Delete("/"+tableName+"/{id}", info)
			if shortName != tableName {
				r.Delete("/"+shortName+"/{id}", info)
			}
		}
	}

	r.Get("/{tableName}/list", Swagger{
		Controller: listModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: "fields", In: Query, Array: true},
			{Name: "keyword", In: Query},
			{Name: "ids", In: Query, Array: true},
			{Name: "filter", In: Query, Map: true},
			{Name: "like", In: Query, Array: true},
			{Name: `page`, In: Query, Type: Int, Default: 1},
			{Name: `size`, In: Query, Type: Int},
			{Name: "all", In: Query, Type: Boolean, Default: false},
			{Name: "order", In: Query, Default: ""},
			{Name: "direction", In: Query, Default: "desc"},
		},
		Response: PageResponse(nil),
	})
	r.Put("/{tableName}/{id}/sort", Swagger{
		Controller: sortModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `id`, In: Path},
			{Name: "sorted", In: Body, Type: String},
		},
		Response: PageResponse(nil),
	})
	r.Put("/{tableName}/{id}/link", Swagger{
		Controller: linkModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `id`, In: Path},
			{Name: "targetTable", In: Body, Type: String},
			{Name: "targetId", In: Body, Type: String},
			{Name: "desc", In: Body, Type: String},
		},
		Response: PageResponse(nil),
	})
	r.Post("/{tableName}/", Swagger{
		Controller: addModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `data`, In: All, Required: true},
		},
		Response: ApiResponse,
	})
	r.Get("/{tableName}/{id}", Swagger{
		Controller: getModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `id`, In: Path},
		},
		Response: ApiResponse,
	})
	r.Put("/{tableName}/{id}", Swagger{
		Controller: editModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `id`, In: Path},
			{Name: `data`, In: All, Required: true},
		},
		Response: ApiResponse,
	})
	r.Delete("/{tableName}/{id}", Swagger{
		Controller: deleteModel,
		Params: []Param{
			{Name: "app", In: Query, Default: app},
			{Name: "tableName", In: Path},
			{Name: `id`, In: Path},
		},
		Response: ApiResponse,
	})
	return nil
}
